﻿using System;
using System.Data.SqlClient;
using PropertyExpression.Common;

namespace PropertyExpression.ControllableQuery
{
    public class QueryContext
    {
        private readonly SqlCommand sqlCommand;
        private readonly SqlParameterNameGenerator sqlParameterNameGenerator = new SqlParameterNameGenerator();

        public QueryContext(SqlCommand sqlCommand)
        {
            this.sqlCommand = sqlCommand;
        }

        public IParam<T> CreateParam<T>(Func<string, SqlParameter> sqlParameterFunc, Func<T, object> toSqlParameterValueFunc)
        {
            return new Param<T>(this, sqlParameterFunc, toSqlParameterValueFunc);
        }

        private class Param<T> : IParam<T>
        {
            private readonly QueryContext parent;
            private readonly Func<string, SqlParameter> sqlParameterFunc;
            private readonly Func<T, object> toSqlParameterValueFunc;

            public Param(QueryContext parent, Func<string, SqlParameter> sqlParameterFunc, Func<T, object> toSqlParameterValueFunc)
            {
                this.parent = parent;
                this.sqlParameterFunc = sqlParameterFunc;
                this.toSqlParameterValueFunc = toSqlParameterValueFunc;
            }

            private IOptionalValue<T> value = OptionalValue.Nothing<T>();
            public T Value
            {
                set
                {
                    this.value = value.AsOptionalValue();
                    if (ValueChanged != null) ValueChanged(value);
                }
            }

            private event Action<T> ValueChanged;

            public ISqlParam<T> GenerateSqlParam()
            {
                return GetSqlParam(parent.sqlParameterNameGenerator.GetNextName());
            }

            public ISqlParam<T> GetSqlParam(string parameterName)
            {
                return new SqlParam(this, parameterName);
            }

            private class SqlParam : ISqlParam<T>
            {
                private readonly Lazy<string> toSqlResult;

                public SqlParam(Param<T> parent, string parameterName)
                {
                    toSqlResult = new Lazy<string>(
                        () =>
                            {
                                var sqlParameter = parent.sqlParameterFunc(parameterName);
                                parent.value.ProcessValue(value => sqlParameter.Value = parent.toSqlParameterValueFunc(value));
                                parent.ValueChanged +=
                                    value => sqlParameter.Value = parent.toSqlParameterValueFunc(value);
                                parent.parent.sqlCommand.Parameters.Add(sqlParameter);
                                return parameterName;
                            }
                        );
                }

                public string ToSql()
                {
                    return toSqlResult.Value;
                }

                public override string ToString()
                {
                    return ToSql();
                }
            }

            public override string ToString()
            {
                return this.ToSql();
            }
        }
    }
}